﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Linq;
using System.Text;
using Equinoxe.EZNFC.Library.Windows;

// ReSharper disable CheckNamespace
namespace Equinoxe.EZNFC.Library.Tags
// ReSharper restore CheckNamespace
{
    /// <summary>
    /// 
    /// </summary>
    /// <remarks></remarks>
    public class Mifare1K : INFCTag
    {
        #region Fields
        /// <summary>
        /// The NFC reader device communicating with the tag.
        /// </summary>
        private readonly INFCDevice _nfcDevice;

        /// <summary>
        /// UID of the tag.
        /// </summary>
        private byte[] _UID = new byte[4];

        /// <summary>
        /// Basic command.
        /// </summary>
        private readonly byte[] _commandBasic = new byte[] { 0xD4, 0x40 };

        /// <summary>
        /// Tag target number for NFC device.
        /// </summary>
        private readonly byte[] _targetNumber = new byte[] { 0x00 };

        /// <summary>
        /// A key authentication command.
        /// </summary>
        private readonly byte[] _commandAuthA = new byte[] { 0x60 };

        /// <summary>
        /// B key authentication command.
        /// </summary>
        private readonly byte[] _commandAuthB = new byte[] { 0x61 };

        /// <summary>
        /// Read block command.
        /// </summary>
        private readonly byte[] _commandReadBlock = new byte[] { 0x30 };

        /// <summary>
        /// Write block command.
        /// </summary>
        private readonly byte[] _commandWriteBlock = new byte[] { 0xA0 };

        /// <summary>
        /// Increment command.
        /// </summary>
        private readonly byte[] _commandIncrement = new byte[] { 0xC1 };

        /// <summary>
        /// Decrement command.
        /// </summary>
        private readonly byte[] _commandDecrement = new byte[] { 0xC0 };

        /// <summary>
        /// Commit command.
        /// </summary>
        private readonly byte[] _commandCommit = new byte[] { 0xB0 };

        /// <summary>
        /// Rollback command.
        /// </summary>
        private readonly byte[] _commandRollback = new byte[] { 0xC2 };

        /// <summary>
        /// Logoff command.
        /// </summary>
        private readonly byte[] _commandLogoff = new byte[] { 0xD4, 0x44 };

        #endregion

        /// <summary>
        /// A key authentication message.
        /// </summary>
        private byte[] _messageAuthA;

        /// <summary>
        /// A key authentication message.
        /// </summary>
        /// <remarks></remarks>
        private byte[] MessageAuthA
        {
            get { return _messageAuthA ?? (_messageAuthA = _commandBasic.Concat(_targetNumber).Concat(_commandAuthA).ToArray()); }
        }

        /// <summary>
        /// B key authentication message.
        /// </summary>
        private byte[] _messageAuthB;

        /// <summary>
        /// B key authentication message.
        /// </summary>
        /// <remarks></remarks>
        private byte[] MessageAuthB
        {
            get { return _messageAuthB ?? (_messageAuthB = _commandBasic.Concat(_targetNumber).Concat(_commandAuthB).ToArray()); }
        }

        /// <summary>
        /// The read block message.
        /// </summary>
        private byte[] _messageReadBlock;

        /// <summary>
        /// Gets the read block message.
        /// </summary>
        /// <remarks></remarks>
        private byte[] MessageReadBlock
        {
            get { return _messageReadBlock ?? (_messageReadBlock = _commandBasic.Concat(_targetNumber).Concat(_commandReadBlock).ToArray()); }
        }

        /// <summary>
        /// The write block message.
        /// </summary>
        private byte[] _messageWriteBlock;

        /// <summary>
        /// Gets the write block message.
        /// </summary>
        /// <remarks></remarks>
        private byte[] MessageWriteBlock
        {
            get { return _messageWriteBlock ?? (_messageWriteBlock = _commandBasic.Concat(_targetNumber).Concat(_commandWriteBlock).ToArray()); }
        }

        /// <summary>
        /// The message increment.
        /// </summary>
        private byte[] _messageIncrement;

        /// <summary>
        /// Gets the message increment.
        /// </summary>
        /// <remarks></remarks>
        private byte[] MessageIncrement
        {
            get { return _messageIncrement ?? (_messageIncrement = _commandBasic.Concat(_targetNumber).Concat(_commandIncrement).ToArray()); }
        }

        /// <summary>
        /// The message decrement.
        /// </summary>
        private byte[] _messageDecrement;

        /// <summary>
        /// Gets the message decrement.
        /// </summary>
        /// <remarks></remarks>
        private byte[] MessageDecrement
        {
            get { return _messageDecrement ?? (_messageDecrement = _commandBasic.Concat(_targetNumber).Concat(_commandDecrement).ToArray()); }
        }

        /// <summary>
        /// The message commit.
        /// </summary>
        private byte[] _messageCommit;

        /// <summary>
        /// Gets the message commit.
        /// </summary>
        /// <remarks></remarks>
        private byte[] MessageCommit
        {
            get { return _messageCommit ?? (_messageCommit = _commandBasic.Concat(_targetNumber).Concat(_commandCommit).ToArray()); }
        }

        /// <summary>
        /// The message rollback.
        /// </summary>
        private byte[] _messageRollback;

        /// <summary>
        /// Gets the message rollback.
        /// </summary>
        /// <remarks></remarks>
        private byte[] MessageRollback
        {
            get { return _messageRollback ?? (_messageRollback = _commandBasic.Concat(_targetNumber).Concat(_commandRollback).ToArray()); }
        }

        /// <summary>
        /// The message logoff.
        /// </summary>
        private byte[] _messageLogoff;

        /// <summary>
        /// Gets the message logoff.
        /// </summary>
        /// <remarks></remarks>
        private byte[] MessageLogoff
        {
            get { return _messageLogoff ?? (_messageLogoff = _commandLogoff.Concat(_targetNumber).ToArray()); }
        }

        /// <summary>
        /// T
        /// </summary>
        private TagInfo _tagInfo;

        #region Properties

        /// <summary>
        /// Gets the tag info.
        /// </summary>
        /// <remarks></remarks>
        public TagInfo TagInfo
        {
            get { return _tagInfo; }
        }

        /// <summary>
        /// Gets the tag ID.
        /// </summary>
        /// <remarks></remarks>
        public uint TagID
        {
            get { return (uint)Tools.ByteArrayToInt(_UID); }
        }

        #endregion


        /// <summary>
        /// Initializes a new instance of the <see cref="Mifare1K"/> class.
        /// </summary>
        /// <param name="device">The NFC device.</param>
        /// <param name="pollingdata">The pollingdata received from the NFC device.</param>
        /// <remarks></remarks>
        public Mifare1K(INFCDevice device, byte[] pollingdata)
        {
            _nfcDevice = device;
            _UID[0] = pollingdata[8];
            _UID[1] = pollingdata[9];
            _UID[2] = pollingdata[10];
            _UID[3] = pollingdata[11];

            _targetNumber[0] = pollingdata[3];

            _tagInfo = new Mifare1KInfo();
        }

        /// <summary>
        /// Sends the message.
        /// </summary>
        /// <param name="sendbuffer">The sendbuffer.</param>
        /// <param name="sendbufferlength">The sendbuffer length.</param>
        /// <param name="receivebuffer">The receivebuffer.</param>
        /// <param name="receivebufferlength">The receivebuffer length.</param>
        /// <returns></returns>
        /// <remarks></remarks>
        public void SendMessage(byte[] sendbuffer, int sendbufferlength, byte[] receivebuffer, int receivebufferlength)
        {
            _nfcDevice.SendMessage(sendbuffer, sendbufferlength, receivebuffer, ref receivebufferlength);
        }

        #region Authentication

        /// <summary>
        /// Authenticates with a specified key.
        /// </summary>
        /// <param name="keyToUse">Which key to use, A or B.</param>
        /// <param name="block">The block.</param>
        /// <param name="key">The key.</param>
        /// <param name="keyLength">Length of the key.</param>
        /// <returns></returns>
        /// <remarks></remarks>
        public void Authenticate(AuthenticatingKey keyToUse, int block, byte[] key, int keyLength)
        {
            var byteblock = new[] { (byte)block };

            var receivedbuffer = new byte[255];

            var receivedbufferlength = receivedbuffer.Length;

            // send buffer = message + key + UID
            var sendbuffer =
                (keyToUse == AuthenticatingKey.KeyA ? MessageAuthA : MessageAuthB).Concat(byteblock).Concat(key).
                    Concat(_UID).ToArray();

            _nfcDevice.SendMessage(sendbuffer, sendbuffer.Length, receivedbuffer, ref receivedbufferlength);

            Tools.WriteTrace("Authenticate", sendbuffer, sendbuffer.Length, receivedbuffer, receivedbufferlength);

            if ((ErrorCodes)receivedbuffer[2] != ErrorCodes.NoError)
            {
                throw new TagException { ErrorCode = (ErrorCodes)receivedbuffer[2] };
            }
        }

        /// <summary>
        /// Authenticates  with a specified key.
        /// </summary>
        /// <param name="keyToUse">Which key to use.</param>
        /// <param name="sector">The sector.</param>
        /// <param name="block">The block.</param>
        /// <param name="key">The key.</param>
        /// <param name="keyLength">Length of the key.</param>
        /// <returns></returns>
        /// <remarks></remarks>
        public void Authenticate(AuthenticatingKey keyToUse, int sector, int block, byte[] key, int keyLength)
        {
            Authenticate(keyToUse, TagInfo.GetBlockNumber(sector, block), key, keyLength);
        }

        /// <summary>
        /// Authenticates with a specified key.
        /// </summary>
        /// <param name="keyToUse">The key to use.</param>
        /// <param name="block">The block.</param>
        /// <param name="key">The key.</param>
        /// <returns></returns>
        /// <remarks></remarks>
        public void Authenticate(AuthenticatingKey keyToUse, int block, string key)
        {
            key = key.Replace(" ", "").Replace("0x", "");

            if (key.Length != TagInfo.KeySize * 2)
            {
                throw new TagException {ErrorCode = ErrorCodes.KeySizeError};
            }
            var keybuffer = new byte[TagInfo.KeySize];

            keybuffer[0] = byte.Parse(key.Substring(0, 2), NumberStyles.HexNumber);
            keybuffer[1] = byte.Parse(key.Substring(2, 2), NumberStyles.HexNumber);
            keybuffer[2] = byte.Parse(key.Substring(4, 2), NumberStyles.HexNumber);
            keybuffer[3] = byte.Parse(key.Substring(6, 2), NumberStyles.HexNumber);
            keybuffer[4] = byte.Parse(key.Substring(8, 2), NumberStyles.HexNumber);
            keybuffer[5] = byte.Parse(key.Substring(10, 2), NumberStyles.HexNumber);

            Authenticate(keyToUse, block, keybuffer, keybuffer.Length);
        }

        /// <summary>
        /// Authenticates with a specified key.
        /// </summary>
        /// <param name="keyToUse">Which key to use.</param>
        /// <param name="sector">The sector.</param>
        /// <param name="block">The block.</param>
        /// <param name="key">The key.</param>
        /// <returns></returns>
        /// <remarks></remarks>
        public void Authenticate(AuthenticatingKey keyToUse, int sector, int block, string key)
        {
            Authenticate(keyToUse, TagInfo.GetBlockNumber(sector,block), key);
        }

        /// <summary>
        /// Copies the value block to another value block.
        /// </summary>
        /// <param name="sourcesector">The source sector.</param>
        /// <param name="sourceblock">The source block.</param>
        /// <param name="destinationsector">The destination sector.</param>
        /// <param name="destinationblock">The destination block.</param>
        /// <returns></returns>
        /// <remarks></remarks>
        public void CopyValueBlock(int sourcesector, int sourceblock, int destinationsector, int destinationblock)
        {
            CopyValueBlock(TagInfo.GetBlockNumber(sourcesector, sourceblock),  TagInfo.GetBlockNumber(destinationsector,destinationblock));
        }

        /// <summary>
        /// Logoffs this instance.
        /// </summary>
        /// <returns></returns>
        /// <remarks></remarks>
        public void Logoff()
        {
            var receivedbuffer = new byte[255];

            var receivedbufferlength = receivedbuffer.Length;

            var sendbuffer = MessageLogoff.ToArray();

            _nfcDevice.SendMessage(sendbuffer, sendbuffer.Length, receivedbuffer, ref receivedbufferlength);

            Tools.WriteTrace("Logoff", sendbuffer, sendbuffer.Length, receivedbuffer, receivedbufferlength);

            if ((ErrorCodes)receivedbuffer[2] != ErrorCodes.NoError)
            {
                throw new TagException { ErrorCode = (ErrorCodes)receivedbuffer[2] };
            }
        }

        #endregion

        #region Read/Write Block
        /// <summary>
        /// Reads a block.
        /// </summary>
        /// <param name="block">The block.</param>
        /// <param name="receivedbuffer">The receivedbuffer.</param>
        /// <param name="receivedbufferlength">The receivedbuffer length.</param>
        /// <returns></returns>
        /// <remarks></remarks>
        public void ReadBlock(int block, byte[] receivedbuffer, ref int receivedbufferlength)
        {
            var byteblock = new[] { (byte)block };

            var sendbuffer = MessageReadBlock.Concat(byteblock).ToArray();

             _nfcDevice.SendMessage(sendbuffer, sendbuffer.Length, receivedbuffer, ref receivedbufferlength);

             Tools.WriteTrace("ReadBlock", sendbuffer, sendbuffer.Length, receivedbuffer, receivedbufferlength);


            if ((ErrorCodes)receivedbuffer[2] != ErrorCodes.NoError)
            {
                throw new TagException { ErrorCode = (ErrorCodes)receivedbuffer[2] };
            }
        }

        /// <summary>
        /// Reads a block.
        /// </summary>
        /// <param name="sector">The sector.</param>
        /// <param name="block">The block.</param>
        /// <param name="buffer">The buffer.</param>
        /// <param name="bufferlength">The bufferlength.</param>
        /// <returns></returns>
        /// <remarks></remarks>
        public void ReadBlock(int sector, int block, byte[] buffer, ref int bufferlength)
        {
            ReadBlock(TagInfo.GetBlockNumber(sector,block), buffer, ref bufferlength);
        }

        /// <summary>
        /// Reads a block into a string.
        /// </summary>
        /// <param name="block">The block.</param>
        /// <returns></returns>
        /// <remarks></remarks>
        public string ReadBlockString(int block)
        {
            string data;

            var buffer = new byte[255];

            var bufferlength = buffer.Length;

            ReadBlock(block, buffer, ref bufferlength);

            data = Encoding.Unicode.GetString(buffer, 3, bufferlength - 3);

            return data;
        }

        /// <summary>
        /// Reads a block into a string.
        /// </summary>
        /// <param name="sector">The sector.</param>
        /// <param name="block">The block.</param>
        /// <returns></returns>
        /// <remarks></remarks>
        public string ReadBlockString(int sector, int block)
        {
            return ReadBlockString( TagInfo.GetBlockNumber(sector,block));
        }

        /// <summary>
        /// Writes a block.
        /// </summary>
        /// <param name="block">The block.</param>
        /// <param name="buffer">The buffer.</param>
        /// <param name="bufferlength">The bufferlength.</param>
        /// <returns></returns>
        /// <remarks></remarks>
        public void WriteBlock(int block, byte[] buffer, ref int bufferlength)
        {
            var receivedbuffer = new byte[255];
            var receivedbufferlength = receivedbuffer.Length;

            var byteblock = new[] { (byte)block };

            var sendbuffer = MessageWriteBlock.Concat(byteblock).Concat(buffer.Take(16)).ToArray();

            _nfcDevice.SendMessage(sendbuffer, sendbuffer.Length, receivedbuffer, ref receivedbufferlength);

            Tools.WriteTrace("WriteBlock", sendbuffer, sendbuffer.Length, receivedbuffer, receivedbufferlength);


            if ((ErrorCodes)receivedbuffer[2] != ErrorCodes.NoError)
            {
                throw new TagException { ErrorCode = (ErrorCodes)receivedbuffer[2] };
            }
        }

        /// <summary>
        /// Writes a block.
        /// </summary>
        /// <param name="sector">The sector.</param>
        /// <param name="block">The block.</param>
        /// <param name="buffer">The buffer.</param>
        /// <param name="bufferlength">The bufferlength.</param>
        /// <returns></returns>
        /// <remarks></remarks>
        public void WriteBlock(int sector, int block, byte[] buffer, ref int bufferlength)
        {
            WriteBlock( TagInfo.GetBlockNumber(sector, block), buffer, ref bufferlength);
        }

        /// <summary>
        /// Writes a string in a block.
        /// </summary>
        /// <param name="block">The block.</param>
        /// <param name="data">The data.</param>
        /// <returns></returns>
        /// <remarks></remarks>
        public void WriteBlockString(int block, string data)
        {
            if (data.Length != 8)
                throw new TagException {ErrorCode = ErrorCodes.InvalidParameter};

            var buffer = Encoding.Unicode.GetBytes(data);

            var bufferlength = buffer.Length;

            WriteBlock(block, buffer, ref bufferlength);
        }

        /// <summary>
        /// Writes a string in a block.
        /// </summary>
        /// <param name="sector">The sector.</param>
        /// <param name="block">The block.</param>
        /// <param name="data">The data.</param>
        /// <returns></returns>
        /// <remarks></remarks>
        public void WriteBlockString(int sector, int block, string data)
        {
            WriteBlockString( TagInfo.GetBlockNumber(sector, block), data);
        }

        #endregion

        #region Value Blocks

        /// <summary>
        /// Reads a value block.
        /// </summary>
        /// <param name="block">The block.</param>
        /// <param name="check">if set to <c>true</c> [check].</param>
        /// <returns></returns>
        /// <remarks></remarks>
        public int ReadValueBlock(int block, bool check = false)
        {
            int value;

            var receivedbuffer = new byte[255];
            var receivedbufferlength = receivedbuffer.Length;

            var byteblock = new[] { (byte)block };

            var sendbuffer = MessageReadBlock.Concat(byteblock).ToArray();

            _nfcDevice.SendMessage(sendbuffer, sendbuffer.Length, receivedbuffer, ref receivedbufferlength);

            Tools.WriteTrace("ReadValueBlock", sendbuffer, sendbuffer.Length, receivedbuffer, receivedbufferlength);


            if ((ErrorCodes)receivedbuffer[2] != ErrorCodes.NoError)
            {
                throw new TagException { ErrorCode = (ErrorCodes)receivedbuffer[2] };
            }

            var block1 = new[] { receivedbuffer[3], receivedbuffer[4], receivedbuffer[5], receivedbuffer[6] };

            value = Tools.ByteArrayToInt(block1);

            if (check)
            {
                var block2 = new[] { receivedbuffer[7], receivedbuffer[8], receivedbuffer[9], receivedbuffer[10] };
                var block3 = new[] { receivedbuffer[11], receivedbuffer[12], receivedbuffer[13], receivedbuffer[14] };

                var value2 = Tools.ComplementByteArrayToInt(block2);

                var value3 = Tools.ByteArrayToInt(block3);

                if (value2 != value || value3 != value)
                    throw new TagException {ErrorCode = ErrorCodes.OperationFailure};
            }

            return value;
        }

        /// <summary>
        /// Reads a value block.
        /// </summary>
        /// <param name="sector">The sector.</param>
        /// <param name="block">The block.</param>
        /// <param name="check">if set to <c>true</c> [check].</param>
        /// <returns></returns>
        /// <remarks></remarks>
        public int ReadValueBlock(int sector, int block, bool check = false)
        {
            return ReadValueBlock( TagInfo.GetBlockNumber(sector, block), check);
        }

        /// <summary>
        /// Writes a value block.
        /// </summary>
        /// <param name="block">The block.</param>
        /// <param name="value">The value.</param>
        /// <param name="adressblock">The adressblock.</param>
        /// <returns></returns>
        /// <remarks></remarks>
        public void WriteValueBlock(int block, int value, int adressblock = 255)
        {
            adressblock = block;

            var receivedbuffer = new byte[255];
            var receivedbufferlength = receivedbuffer.Length;

            var byteblock = new[] { (byte)block };

            var buffer = Tools.IntToByteArray(value).Concat(Tools.IntToComplementByteArray(value)).Concat(Tools.IntToByteArray(value));

            var adressbuffer = new[] { (byte)adressblock, (byte)(255 - adressblock), (byte)adressblock, (byte)(255 - adressblock) };

            var sendbuffer = MessageWriteBlock.Concat(byteblock).Concat(buffer).Concat(adressbuffer).ToArray();

            _nfcDevice.SendMessage(sendbuffer, sendbuffer.Length, receivedbuffer, ref receivedbufferlength);

            Tools.WriteTrace("WriteValueBlock", sendbuffer, sendbuffer.Length, receivedbuffer, receivedbufferlength);


            if ((ErrorCodes)receivedbuffer[2] != ErrorCodes.NoError)
            {
                throw new TagException { ErrorCode = (ErrorCodes)receivedbuffer[2] };
            }
        }

        /// <summary>
        /// Writes a value block.
        /// </summary>
        /// <param name="sector">The sector.</param>
        /// <param name="block">The block.</param>
        /// <param name="value">The value.</param>
        /// <param name="adressblock">The adressblock.</param>
        /// <returns></returns>
        /// <remarks></remarks>
        public void WriteValueBlock(int sector, int block, int value, int adressblock = 255)
        {
            WriteValueBlock( TagInfo.GetBlockNumber(sector, block), value, adressblock);
        }

        /// <summary>
        /// Increments a value block.
        /// </summary>
        /// <param name="block">The block.</param>
        /// <param name="value">The value.</param>
        /// <returns></returns>
        /// <remarks></remarks>
        public void IncrementValueBlock(int block, int value = 1)
        {
            var receivedbuffer = new byte[255];
            var receivedbufferlength = receivedbuffer.Length;

            var byteblock = new[] { (byte)block };

            var buffer = Tools.IntToByteArray(value);

            var sendbuffer = MessageIncrement.Concat(byteblock).Concat(buffer).ToArray();

            _nfcDevice.SendMessage(sendbuffer, sendbuffer.Length, receivedbuffer, ref receivedbufferlength);

            Tools.WriteTrace("IncrementValueBlock", sendbuffer, sendbuffer.Length, receivedbuffer, receivedbufferlength);


            if ((ErrorCodes)receivedbuffer[2] != ErrorCodes.NoError)
            {
                throw new TagException { ErrorCode = (ErrorCodes)receivedbuffer[2] };
            }
        }

        /// <summary>
        /// Increments a value block.
        /// </summary>
        /// <param name="sector">The sector.</param>
        /// <param name="block">The block.</param>
        /// <param name="value">The value.</param>
        /// <returns></returns>
        /// <remarks></remarks>
        public void IncrementValueBlock(int sector, int block, int value = 1)
        {
            IncrementValueBlock( TagInfo.GetBlockNumber(sector, block), value);
        }

        /// <summary>
        /// Decrements a value block.
        /// </summary>
        /// <param name="block">The block.</param>
        /// <param name="value">The value.</param>
        /// <returns></returns>
        /// <remarks></remarks>
        public void DecrementValueBlock(int block, int value = 1)
        {
            var receivedbuffer = new byte[255];
            var receivedbufferlength = receivedbuffer.Length;

            var byteblock = new[] { (byte)block };

            var buffer = Tools.IntToByteArray(value);

            var sendbuffer = MessageDecrement.Concat(byteblock).Concat(buffer).ToArray();

            _nfcDevice.SendMessage(sendbuffer, sendbuffer.Length, receivedbuffer, ref receivedbufferlength);

            Tools.WriteTrace("DecrementValueBlock", sendbuffer, sendbuffer.Length, receivedbuffer, receivedbufferlength);


            if ((ErrorCodes)receivedbuffer[2] != ErrorCodes.NoError)
            {
                throw new TagException { ErrorCode = (ErrorCodes)receivedbuffer[2] };
            }
        }

        /// <summary>
        /// Decrements a value block.
        /// </summary>
        /// <param name="sector">The sector.</param>
        /// <param name="block">The block.</param>
        /// <param name="value">The value.</param>
        /// <returns></returns>
        /// <remarks></remarks>
        public void DecrementValueBlock(int sector, int block, int value = 1)
        {
            DecrementValueBlock( TagInfo.GetBlockNumber(sector, block), value);
        }


        /// <summary>
        /// Commits a block operation.
        /// </summary>
        /// <param name="block">The block.</param>
        /// <returns></returns>
        /// <remarks></remarks>
        public void CommitBlockOperation(int block)
        {
            var receivedbuffer = new byte[255];
            var receivedbufferlength = receivedbuffer.Length;

            var byteblock = new[] { (byte)block };

            var sendbuffer = MessageCommit.Concat(byteblock).ToArray();

            _nfcDevice.SendMessage(sendbuffer, sendbuffer.Length, receivedbuffer, ref receivedbufferlength);

            Tools.WriteTrace("CommitBlockOperation", sendbuffer, sendbuffer.Length, receivedbuffer, receivedbufferlength);


            if ((ErrorCodes)receivedbuffer[2] != ErrorCodes.NoError)
            {
                throw new TagException { ErrorCode = (ErrorCodes)receivedbuffer[2] };
            }
        }

        /// <summary>
        /// Commits a block operation.
        /// </summary>
        /// <param name="sector">The sector.</param>
        /// <param name="block">The block.</param>
        /// <returns></returns>
        /// <remarks></remarks>
        public void CommitBlockOperation(int sector, int block)
        {
            CommitBlockOperation( TagInfo.GetBlockNumber(sector, block));
        }

        /// <summary>
        /// Rollbacks a block operation.
        /// </summary>
        /// <param name="block">The block.</param>
        /// <returns></returns>
        /// <remarks></remarks>
        public void RollbackBlockOperation(int block)
        {
            var receivedbuffer = new byte[255];
            var receivedbufferlength = receivedbuffer.Length;

            var byteblock = new[] { (byte)block };

            var sendbuffer = MessageRollback.Concat(byteblock).ToArray();

            _nfcDevice.SendMessage(sendbuffer, sendbuffer.Length, receivedbuffer, ref receivedbufferlength);

            Tools.WriteTrace("RollbackBlockOperation", sendbuffer, sendbuffer.Length, receivedbuffer, receivedbufferlength);


            if ((ErrorCodes)receivedbuffer[2] != ErrorCodes.NoError)
            {
                throw new TagException { ErrorCode = (ErrorCodes)receivedbuffer[2] };
            }
        }

        /// <summary>
        /// Rollbacks a block operation.
        /// </summary>
        /// <param name="sector">The sector.</param>
        /// <param name="block">The block.</param>
        /// <returns></returns>
        /// <remarks></remarks>
        public void RollbackBlockOperation(int sector, int block)
        {
            RollbackBlockOperation( TagInfo.GetBlockNumber(sector, block));
        }


        /// <summary>
        /// Copies the value block to another value block.
        /// </summary>
        /// <param name="sourceblock">The source block.</param>
        /// <param name="destinationblock">The destination block.</param>
        /// <returns></returns>
        /// <remarks></remarks>
        public void CopyValueBlock(int sourceblock, int destinationblock)
        {
            RollbackBlockOperation(sourceblock);

             CommitBlockOperation(destinationblock);
        }

        #endregion


        #region AcessBytes

        /// <summary>
        /// Sets the block access conditions.
        /// </summary>
        /// <param name="sector">The sector.</param>
        /// <param name="conditions">The access conditions.</param>
        /// <returns></returns>
        /// <remarks></remarks>
        public void SetBlockAccess(int sector, SectorAccessConditions conditions)
        {
            var bufferlength = 255;
            var buffer = new byte[bufferlength];

            // get block data
            ReadBlock(sector, 3, buffer, ref bufferlength);

            // convert conditions to bytes
            var accessconditionsbuffer = conditions.ToByteArray();

            // insert in block data
            buffer[6] = accessconditionsbuffer[0];
            buffer[7] = accessconditionsbuffer[1];
            buffer[8] = accessconditionsbuffer[2];
            buffer[9] = accessconditionsbuffer[3];

            //write block data
             WriteBlock(sector, 3, buffer, ref bufferlength);
        }

        public SectorAccessConditions GetBlockAccess(int sector)
        {
            var bufferlength = 255;
            var buffer = new byte[bufferlength];

            // get block data
            ReadBlock(sector, 3, buffer, ref bufferlength);

            // convert bytes to conditions:
            return SectorAccessConditions.FromByteArray(buffer);
        }

        #endregion

        private class Mifare1KInfo : TagInfo
        {
            public override int NumberOfKeys
            {
                get { return 2; }
            }

            public override int KeySize
            {
                get { return 6; }
            }

            public override int NumberOfSectors
            {
                get { return 16; }
            }

            public override TagType TagType
            {
                get { return TagType.Mifare1K; }
            }

            public override string TagName
            {
                get { return "MiFare 1K"; }
            }

            public override string Manufacturer
            {
                get { return string.Empty; }
            }

            private ReadOnlyCollection<TagBlock> _blocksinfo;

            public override ReadOnlyCollection<TagBlock> BlocksInfo
            {
                get { return _blocksinfo; }
            }



            public Mifare1KInfo()
            {
                var blockinfo = new List<TagBlock> { new TagBlock(0, 15, 4, 16) };
                _blocksinfo = new ReadOnlyCollection<TagBlock>(blockinfo);
            }
        }

        // TODO : clean code
        // TODO : doc
    }


    /// <summary>
    /// Access conditions enum for data block
    /// </summary>
    /// <remarks></remarks>
    public enum Mifare1KDataBlockAccessConditions
    {
        // ReSharper disable InconsistentNaming
        rAB_wAB_iAB_dtrAB = 0,
        rAB = 10,
        rAB_wB = 100,
        rAB_wB_iB_drtAB = 110,
        rAB_dtrAB = 1,
        rB_wB = 11,
        rB = 101,
        Nothing = 111
        // ReSharper restore InconsistentNaming
    }

    /// <summary>
    /// Access conditions enum for trailer block
    /// </summary>
    /// <remarks></remarks>
    public enum Mifare1KTrailerBlockAccessConditions
    {
        // ReSharper disable InconsistentNaming
        kawA_abrA_kbrA_knwA = 0,
        abwA_kbrA = 10,
        kawB_abrAB_kbwB = 100,
        abrAB = 110,
        kawA_abrA_abwA_kbrA_kbwA = 001,
        kawB_abrAB_abwB_kbwB = 11,
        abrAB_abwB,
        abrAB2 = 111
        // ReSharper restore InconsistentNaming
    }

    /// <summary>
    /// Class describing access conditions for a block
    /// </summary>
    /// <remarks></remarks>
    public class SectorAccessConditions
    {
        /// <summary>
        /// Gets or sets the trailer block access conditions.
        /// </summary>
        /// <value>The trailer block.</value>
        /// <remarks></remarks>
        public Mifare1KTrailerBlockAccessConditions TrailerBlock { get; set; }

        /// <summary>
        /// Gets or sets the block0 access conditions.
        /// </summary>
        /// <value>The block0.</value>
        /// <remarks></remarks>
        public Mifare1KDataBlockAccessConditions Block0 { get; set; }

        /// <summary>
        /// Gets or sets the block1 access conditions.
        /// </summary>
        /// <value>The block1.</value>
        /// <remarks></remarks>
        public Mifare1KDataBlockAccessConditions Block1 { get; set; }

        /// <summary>
        /// Gets or sets the block2 access conditions.
        /// </summary>
        /// <value>The block2.</value>
        /// <remarks></remarks>
        public Mifare1KDataBlockAccessConditions Block2 { get; set; }


        /// <summary>
        ///  Access conditions the byte array.
        /// </summary>
        /// <returns></returns>
        /// <remarks></remarks>
        public byte[] ToByteArray()
        {
            var trailerblockaccessstring = ((int)TrailerBlock).ToString("000");

            var block0Accessstring = ((int)Block0).ToString("000");

            var block1Accessstring = ((int)Block1).ToString("000");

            var block2Accessstring = ((int)Block2).ToString("000");

            var trailerblockaccessinvertstring = InvertStringBit(trailerblockaccessstring);

            var block0Accessinvertstring = InvertStringBit(block0Accessstring);

            var block1Accessinvertstring = InvertStringBit(block1Accessstring);

            var block2Accessinvertstring = InvertStringBit(block2Accessstring);

            var byte0 = "" + trailerblockaccessinvertstring[1] + block2Accessinvertstring[1] + block1Accessinvertstring[1] +
                        block0Accessinvertstring[1] + trailerblockaccessinvertstring[0] + block2Accessinvertstring[0] +
                        block1Accessinvertstring[0] + block0Accessinvertstring[0];

            var byte1 = "" + trailerblockaccessstring[0] + block2Accessstring[0] + block1Accessstring[0] +
                        block0Accessstring[0] + trailerblockaccessinvertstring[2] + block2Accessinvertstring[2] +
                        block1Accessinvertstring[2] + block0Accessinvertstring[2];
            var byte2 = "" + trailerblockaccessstring[2] + block2Accessstring[2] + block1Accessstring[2] +
                        block0Accessstring[2] + trailerblockaccessstring[1] + block2Accessstring[1] + block1Accessstring[1] +
                        block0Accessstring[1];

            var buffer = new byte[] { Convert.ToByte(byte0, 2), Convert.ToByte(byte1, 2), Convert.ToByte(byte2, 2) ,0};

            return buffer;
        }

        /// <summary>
        /// Get SectorAccessConditions from a byte array.
        /// </summary>
        /// <param name="buffer">The buffer.</param>
        /// <returns></returns>
        /// <remarks></remarks>
        static public SectorAccessConditions FromByteArray(byte[] buffer)
        {
            var byte7 = string.Format("{0,8}",Convert.ToString(buffer[10], 2)).Replace(' ','0');

            var byte8 = string.Format("{0,8}", Convert.ToString(buffer[11], 2)).Replace(' ', '0');

            var trailerblockaccess = "" + byte7[0] + byte8[4] + byte8[0];

            var block0Access = "" + byte7[3] + byte8[7] + byte8[3];

            var block1Access = "" + byte7[2] + byte8[6] + byte8[2];

            var block2Access = "" +byte7[1] + byte8[5] + byte8[1];

            var obj = new SectorAccessConditions
                          {
                              Block0 = (Mifare1KDataBlockAccessConditions) (Convert.ToInt32(block0Access, 2)),
                              Block1 = (Mifare1KDataBlockAccessConditions) (Convert.ToInt32(block1Access, 2)),
                              Block2 = (Mifare1KDataBlockAccessConditions) (Convert.ToInt32(block2Access, 2)),
                              TrailerBlock =(Mifare1KTrailerBlockAccessConditions) (Convert.ToInt32(trailerblockaccess, 2))
                          };
            return obj;
        }

        /// <summary>
        /// Inverts the string binary representation bit.
        /// </summary>
        /// <param name="source">The string.</param>
        /// <returns></returns>
        /// <remarks></remarks>
        private string InvertStringBit(string source)
        {
            var dummy = new StringBuilder(source.Length);

            foreach (var bit in source.ToCharArray())
            {
                if (bit == '1')
                    dummy.Append('0');
                else
                    dummy.Append('1');
            }

            return dummy.ToString();
        }

    }


}
 